#include <rthw.h>
#include <rtthread.h>
#include <rtdevice.h>

#include <drivers/spi.h>
#include "board.h"
#include "stm32f10x.h"
#include "codec_pcm1770.h"

#define CODEC_I2S_PORT		SPI3
#define CODEC_I2S_IRQ		SPI3_IRQn
#define CODEC_I2S_DMA		DMA2_Channel2
#define CODEC_I2S_DMA_IRQ	DMA2_Channel2_IRQn
#define CODEC_I2S_RCC_APB1	RCC_APB1Periph_SPI3
#define CODEC_I2S_RCC_AHB	RCC_AHBPeriph_DMA2

// I2S3_WS -> PA15
#define CODEC_I2S_WS_PIN	GPIO_Pin_15
#define CODEC_I2S_WS_PORT	GPIOA
// I2S3_CK -> PB3
#define CODEC_I2S_CK_PIN	GPIO_Pin_3
#define CODEC_I2S_CK_PORT	GPIOB
// I2S3_SD -> PB5
#define CODEC_I2S_SD_PIN	GPIO_Pin_5
#define CODEC_I2S_SD_PORT	GPIOB

static void codec_send(rt_uint16_t s_data);

#define DATA_NODE_MAX 5
/* data node for Tx Mode */
struct codec_data_node
{
    rt_uint16_t *data_ptr;
    rt_size_t  data_size;
};

struct codec_device
{
    /* inherit from rt_device */
    struct rt_device parent;

    /* pcm data list */
    struct codec_data_node data_list[DATA_NODE_MAX];
    rt_uint16_t read_index, put_index;

    /* transmitted offset of current data node */
    rt_size_t offset;

    /* spi mode */
    struct rt_spi_device * spi_device;
};
struct codec_device codec;

static int codec_sr_new = 0;

static void NVIC_Configuration(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;

    /* DMA IRQ Channel configuration */
    NVIC_InitStructure.NVIC_IRQChannel = CODEC_I2S_DMA_IRQ;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}

static void GPIO_Configuration(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    /* Disable the JTAG interface and enable the SWJ interface */
    GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);

    // WS
    GPIO_InitStructure.GPIO_Pin = CODEC_I2S_WS_PIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(CODEC_I2S_WS_PORT, &GPIO_InitStructure);

    // CK
    GPIO_InitStructure.GPIO_Pin = CODEC_I2S_CK_PIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(CODEC_I2S_CK_PORT, &GPIO_InitStructure);

    // SD
    GPIO_InitStructure.GPIO_Pin = CODEC_I2S_SD_PIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(CODEC_I2S_SD_PORT, &GPIO_InitStructure);

#ifdef CODEC_USE_MCO
    /*    MCO    configure */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOC,&GPIO_InitStructure);

    RCC_MCOConfig(RCC_MCO_HSE);
#endif
}

static void DMA_Configuration(rt_uint32_t addr, rt_size_t size)
{
    DMA_InitTypeDef DMA_InitStructure;

    /* DMA Channel configuration ----------------------------------------------*/
    DMA_Cmd(CODEC_I2S_DMA, DISABLE);
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&(CODEC_I2S_PORT->DR));
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32) addr;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
    DMA_InitStructure.DMA_BufferSize = size;
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(CODEC_I2S_DMA, &DMA_InitStructure);

    /* Enable SPI DMA Tx request */
    SPI_I2S_DMACmd(CODEC_I2S_PORT, SPI_I2S_DMAReq_Tx, ENABLE);

    DMA_ITConfig(CODEC_I2S_DMA, DMA_IT_TC, ENABLE);
    DMA_Cmd(CODEC_I2S_DMA, ENABLE);
}

static void I2S_Configuration(uint32_t I2S_AudioFreq)
{
    I2S_InitTypeDef I2S_InitStructure;

    /* I2S peripheral configuration */
    I2S_InitStructure.I2S_Standard = I2S_Standard_MSB;
    I2S_InitStructure.I2S_DataFormat = I2S_DataFormat_16b;
    I2S_InitStructure.I2S_MCLKOutput = I2S_MCLKOutput_Enable;
    I2S_InitStructure.I2S_AudioFreq = I2S_AudioFreq;
    I2S_InitStructure.I2S_CPOL = I2S_CPOL_High;

    /* I2S2 configuration */
    I2S_InitStructure.I2S_Mode = I2S_Mode_MasterTx;
    I2S_Init(CODEC_I2S_PORT, &I2S_InitStructure);
}

static void codec_send(rt_uint16_t s_data)
{
    rt_spi_send(codec.spi_device, &s_data, 1);
}

static rt_err_t codec_init(rt_device_t dev)
{
	dev = dev;
	
    return RT_EOK;
}

rt_err_t sample_rate(int sr)
{
    codec_sr_new = sr;

    return RT_EOK;
}

static rt_err_t codec_open(rt_device_t dev, rt_uint16_t oflag)
{
    /* enable I2S */
    I2S_Cmd(CODEC_I2S_PORT, ENABLE);

    return RT_EOK;
}

static rt_err_t codec_close(rt_device_t dev)
{
    return RT_EOK;
}

static rt_err_t codec_control(rt_device_t dev, rt_uint8_t cmd, void *args)
{
    switch (cmd)
    {
    case CODEC_CMD_RESET:
        codec_init(dev);
        break;

    case CODEC_CMD_SAMPLERATE:
        sample_rate(*((int*) args));
        break;

    default:
        return RT_ERROR;
    }
    return RT_EOK;
}

static rt_size_t codec_write(rt_device_t dev, rt_off_t pos,
                             const void* buffer, rt_size_t size)
{
    struct codec_device* device;
    struct codec_data_node* node;
    rt_uint32_t level;
    rt_uint16_t next_index;

    device = (struct codec_device*) dev;
    RT_ASSERT(device != RT_NULL);

    next_index = device->put_index + 1;
    if (next_index >= DATA_NODE_MAX)
        next_index = 0;

    /* check data_list full */
    if (next_index == device->read_index)
    {
        rt_set_errno(-RT_EFULL);
        return 0;
    }

    level = rt_hw_interrupt_disable();
    node = &device->data_list[device->put_index];
    device->put_index = next_index;

    /* set node attribute */
    node->data_ptr = (rt_uint16_t*) buffer;
    node->data_size = size >> 1; /* size is byte unit, convert to half word unit */

    next_index = device->read_index + 1;
    if (next_index >= DATA_NODE_MAX)
        next_index = 0;

    /* check data list whether is empty */
    if (next_index == device->put_index)
    {
        DMA_Configuration((rt_uint32_t) node->data_ptr, node->data_size);
    }
    rt_hw_interrupt_enable(level);

    return size;
}

rt_err_t codec_hw_init(const char * spi_device_name)
{
    struct rt_spi_device * spi_device;

    spi_device = (struct rt_spi_device *)rt_device_find(spi_device_name);
    if(spi_device == RT_NULL)
    {
        rt_kprintf("spi device %s not found!\r\n", spi_device_name);
        return -RT_ENOSYS;
    }
    codec.spi_device = spi_device;

    /* config spi */
    {
        struct rt_spi_configuration cfg;
        cfg.data_width = 16;
        cfg.mode = RT_SPI_MODE_0 | RT_SPI_MSB; /* SPI Compatible Modes 0 */
        cfg.max_hz = 1 * 1000 * 1000; /* 1M */
        rt_spi_configure(spi_device, &cfg);
    }

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE);
    RCC_APB1PeriphClockCmd(CODEC_I2S_RCC_APB1, ENABLE);
    RCC_AHBPeriphClockCmd(CODEC_I2S_RCC_AHB, ENABLE);

    NVIC_Configuration();
    GPIO_Configuration();
    I2S_Configuration(I2S_AudioFreq_44k);

    codec.parent.type = RT_Device_Class_Sound;
    codec.parent.rx_indicate = RT_NULL;
    codec.parent.tx_complete = RT_NULL;
    codec.parent.user_data   = RT_NULL;

    codec.parent.control = codec_control;
    codec.parent.init    = codec_init;
    codec.parent.open    = codec_open;
    codec.parent.close   = codec_close;
    codec.parent.read    = RT_NULL;
    codec.parent.write   = codec_write;

    /* set read_index and put index to 0 */
    codec.read_index = 0;
    codec.put_index = 0;

    /* register the device */
    return rt_device_register(&codec.parent, "snd", RT_DEVICE_FLAG_WRONLY | RT_DEVICE_FLAG_DMA_TX);
}

void codec_dma_isr(void)
{
    /* switch to next buffer */
    rt_uint16_t next_index;
    void* data_ptr;

    next_index = codec.read_index + 1;
    if (next_index >= DATA_NODE_MAX)
        next_index = 0;

    /* save current data pointer */
    data_ptr = codec.data_list[codec.read_index].data_ptr;

    if (codec_sr_new)
    {
        I2S_Configuration(codec_sr_new);
        I2S_Cmd(CODEC_I2S_PORT, ENABLE);
        codec_sr_new = 0;
    }

    codec.read_index = next_index;
    if (next_index != codec.put_index)
    {
        /* enable next dma request */
        DMA_Configuration((rt_uint32_t) codec.data_list[codec.read_index].data_ptr, codec.data_list[codec.read_index].data_size);
    }
    else
    {
        rt_kprintf("*\n");
    }

    /* notify transmitted complete. */
    if (codec.parent.tx_complete != RT_NULL)
    {
        codec.parent.tx_complete(&codec.parent, data_ptr);
    }
}
